﻿using NiuX;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

namespace NiuX.Utils.Utils;

/// <summary>
/// Makes a dictionary out of a given object.
/// </summary>
public static class DictionaryMaker
{
    /// <summary>
    /// Makes a dictionary out of the properties of the given input.
    /// </summary>
    /// <param name="input">Object to make a dictionary out of</param>
    /// <returns>
    /// A dictionary with the keys being the input object property names and the values their respective values
    /// </returns>
    public static IDictionary<string, object> Make(object input)
    {
        switch (input)
        {
            case null:
                throw new ArgumentNullException(nameof(input));
            case IDictionary<string, object> objects:
                return objects;
        }

        var properties = input.GetType().GetProperties();
        var fields = input.GetType().GetFields();
        var members = properties.Cast<MemberInfo>().Concat(fields);

        return members.ToDictionary(m => m.Name, m => GetValue(input, m));
    }

    /// <summary>
    /// Makes a dictionary out of the properties of the given input.
    /// </summary>
    /// <param name="input">Object to make a dictionary out of</param>
    /// <returns>
    /// A dictionary with the keys being the input object property names and the values their respective types and
    /// values
    /// </returns>
    public static IDictionary<string, Tuple<Type, object>> MakeWithType(object input)
    {
        switch (input)
        {
            case null:
                throw new ArgumentNullException(nameof(input));
            case IDictionary<string, object> objects:
                return objects.ToDictionary(
                    kvp => kvp.Key,
                    kvp => kvp.Value == null ?
                        new Tuple<Type, object>(typeof(object), kvp.Value) :
                        new Tuple<Type, object>(kvp.Value.GetType(), kvp.Value)
                );
        }

        var dict = input.GetType().GetProperties().ToDictionary(property => property.Name, property => new Tuple<Type, object>(property.PropertyType, property.GetValue(input, null)));

        foreach (var field in input.GetType().GetFields())
            dict.Add(field.Name, new Tuple<Type, object>(field.FieldType, field.GetValue(input)));

        return dict;
    }

    [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "It's necessary.")]
    private static object GetValue(object obj, MemberInfo member) =>
        member switch
        {
            PropertyInfo info => info.GetValue(obj, null),
            FieldInfo fieldInfo => fieldInfo.GetValue(obj),
            _ => throw new ArgumentException("Passed member is neither a PropertyInfo nor a FieldInfo.")
        };
}